/*
 * Copyright (c) 2007-2012, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: VariableStack.java,v 1.2.4.1 2005/09/10 18:16:22 jeffsuttor Exp $
 */
package com.sun.org.apache.xpath.internal;

import javax.xml.transform.TransformerException;

import com.sun.org.apache.xalan.internal.res.XSLMessages;
import com.sun.org.apache.xpath.internal.objects.XObject;
import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;

/**
 * Defines a class to keep track of a stack for
 * template arguments and variables.
 *
 * <p>This has been changed from the previous incarnations of this
 * class to be fairly low level.</p>
 * @xsl.usage internal
 */
public class VariableStack implements Cloneable {
    /**
     * limitation for 1K
     */
    public static final int CLEARLIMITATION = 1024;

    /**
     * Constructor for a variable stack.
     */
    public VariableStack() {
        reset();
    }

    /**
     * Returns a clone of this variable stack.
     *
     * @return  a clone of this variable stack.
     *
     * @throws CloneNotSupportedException
     */
    public synchronized Object clone() throws CloneNotSupportedException {

        VariableStack vs = (VariableStack) super.clone();

        // I *think* I can get away with a shallow clone here?
        vs._stackFrames = (XObject[]) _stackFrames.clone();
        vs._links = (int[]) _links.clone();

        return vs;
    }

    /**
     * The stack frame where all variables and params will be kept.
     * @serial
     */
    XObject[] _stackFrames = new XObject[XPathContext.RECURSIONLIMIT * 2];

    /**
     * The top of the stack frame (<code>_stackFrames</code>).
     * @serial
     */
    int _frameTop;

    /**
     * The bottom index of the current frame (relative to <code>_stackFrames</code>).
     * @serial
     */
    private int _currentFrameBottom;

    /**
     * The stack of frame positions.  I call 'em links because of distant
     * <a href="http://math.millikin.edu/mprogers/Courses/currentCourses/CS481-ComputerArchitecture/cs481.Motorola68000.html">
     * Motorola 68000 assembler</a> memories.  :-)
     * @serial
     */
    int[] _links = new int[XPathContext.RECURSIONLIMIT];

    /**
     * The top of the links stack.
     */
    int _linksTop;

    /**
     * Get the element at the given index, regardless of stackframe.
     *
     * @param i index from zero.
     *
     * @return The item at the given index.
     */
    public XObject elementAt(final int i) {
        return _stackFrames[i];
    }

    /**
     * Get size of the stack.
     *
     * @return the total size of the execution stack.
     */
    public int size() {
        return _frameTop;
    }

    /**
     * Reset the stack to a start position.
     *
     * @return the total size of the execution stack.
     */
    public void reset() {

        _frameTop = 0;
        _linksTop = 0;

        // Adding one here to the stack of frame positions will allow us always
        // to look one under without having to check if we're at zero.
        // (As long as the caller doesn't screw up link/unlink.)
        _links[_linksTop++] = 0;
        _stackFrames = new XObject[_stackFrames.length];
    }

    /**
     * Set the current stack frame.
     *
     * @param sf The new stack frame position.
     */
    public void setStackFrame(int sf) {
        _currentFrameBottom = sf;
    }

    /**
     * Get the position from where the search should start,
     * which is either the searchStart property, or the top
     * of the stack if that value is -1.
     *
     * @return The current stack frame position.
     */
    public int getStackFrame() {
        return _currentFrameBottom;
    }

    /**
     * Allocates memory (called a stackframe) on the stack; used to store
     * local variables and parameter arguments.
     *
     * <p>I use the link/unlink concept because of distant
     * <a href="http://math.millikin.edu/mprogers/Courses/currentCourses/CS481-ComputerArchitecture/cs481.Motorola68000.html">
     * Motorola 68000 assembler</a> memories.</p>
     *
     * @param size The size of the stack frame allocation.  This ammount should
     * normally be the maximum number of variables that you can have allocated
     * at one time in the new stack frame.
     *
     * @return The bottom of the stack frame, from where local variable addressing
     * should start from.
     */
    public int link(final int size) {

        _currentFrameBottom = _frameTop;
        _frameTop += size;

        if (_frameTop >= _stackFrames.length) {
            XObject newsf[] = new XObject[_stackFrames.length + XPathContext.RECURSIONLIMIT + size];

            System.arraycopy(_stackFrames, 0, newsf, 0, _stackFrames.length);

            _stackFrames = newsf;
        }

        if (_linksTop + 1 >= _links.length) {
            int newlinks[] = new int[_links.length + (CLEARLIMITATION * 2)];

            System.arraycopy(_links, 0, newlinks, 0, _links.length);

            _links = newlinks;
        }

        _links[_linksTop++] = _currentFrameBottom;

        return _currentFrameBottom;
    }

    /**
     * Free up the stack frame that was last allocated with
     * {@link #link(int size)}.
     */
    public void unlink() {
        _frameTop = _links[--_linksTop];
        _currentFrameBottom = _links[_linksTop - 1];
    }

    /**
     * Free up the stack frame that was last allocated with
     * {@link #link(int size)}.
     * @param currentFrame The current frame to set to
     * after the unlink.
     */
    public void unlink(int currentFrame) {
        _frameTop = _links[--_linksTop];
        _currentFrameBottom = currentFrame;
    }

    /**
     * Set a local variable or parameter in the current stack frame.
     *
     *
     * @param index Local variable index relative to the current stack
     * frame bottom.
     *
     * @param val The value of the variable that is being set.
     */
    public void setLocalVariable(int index, XObject val) {
        _stackFrames[index + _currentFrameBottom] = val;
    }

    /**
     * Set a local variable or parameter in the specified stack frame.
     *
     *
     * @param index Local variable index relative to the current stack
     * frame bottom.
     * NEEDSDOC @param stackFrame
     *
     * @param val The value of the variable that is being set.
     */
    public void setLocalVariable(int index, XObject val, int stackFrame) {
        _stackFrames[index + stackFrame] = val;
    }

    /**
     * Get a local variable or parameter in the current stack frame.
     *
     *
     * @param xctxt The XPath context, which must be passed in order to
     * lazy evaluate variables.
     *
     * @param index Local variable index relative to the current stack
     * frame bottom.
     *
     * @return The value of the variable.
     *
     * @throws TransformerException
     */
    public XObject getLocalVariable(XPathContext xctxt, int index) throws TransformerException {

        index += _currentFrameBottom;

        XObject val = _stackFrames[index];

        if (null == val)
            throw new TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_VARIABLE_ACCESSED_BEFORE_BIND, null), xctxt.getSAXLocator());
        // "Variable accessed before it is bound!", xctxt.getSAXLocator());

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return val;
    }

    /**
     * Get a local variable or parameter in the current stack frame.
     *
     *
     * @param index Local variable index relative to the given
     * frame bottom.
     * NEEDSDOC @param frame
     *
     * @return The value of the variable.
     *
     * @throws TransformerException
     */
    public XObject getLocalVariable(int index, int frame) throws TransformerException {

        index += frame;

        XObject val = _stackFrames[index];

        return val;
    }

    /**
     * Get a local variable or parameter in the current stack frame.
     *
     *
     * @param xctxt The XPath context, which must be passed in order to
     * lazy evaluate variables.
     *
     * @param index Local variable index relative to the current stack
     * frame bottom.
     *
     * @return The value of the variable.
     *
     * @throws TransformerException
     */
    public XObject getLocalVariable(XPathContext xctxt, int index, boolean destructiveOK) throws TransformerException {

        index += _currentFrameBottom;

        XObject val = _stackFrames[index];

        if (null == val)
            throw new TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_VARIABLE_ACCESSED_BEFORE_BIND, null), xctxt.getSAXLocator());
        // "Variable accessed before it is bound!", xctxt.getSAXLocator());

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return destructiveOK ? val : val.getFresh();
    }

    /**
     * Tell if a local variable has been set or not.
     *
     * @param index Local variable index relative to the current stack
     * frame bottom.
     *
     * @return true if the value at the index is not null.
     *
     * @throws TransformerException
     */
    public boolean isLocalSet(int index) throws TransformerException {
        return (_stackFrames[index + _currentFrameBottom] != null);
    }

    /** NEEDSDOC Field m_nulls          */
    private static XObject[] m_nulls = new XObject[CLEARLIMITATION];

    /**
     * Use this to clear the variables in a section of the stack.  This is
     * used to clear the parameter section of the stack, so that default param
     * values can tell if they've already been set.  It is important to note that
     * this function has a 1K limitation.
     *
     * @param start The start position, relative to the current local stack frame.
     * @param len The number of slots to be cleared.
     */
    public void clearLocalSlots(int start, int len) {

        start += _currentFrameBottom;

        System.arraycopy(m_nulls, 0, _stackFrames, start, len);
    }

    /**
     * Set a global variable or parameter in the global stack frame.
     *
     *
     * @param index Local variable index relative to the global stack frame
     * bottom.
     *
     * @param val The value of the variable that is being set.
     */
    public void setGlobalVariable(final int index, final XObject val) {
        _stackFrames[index] = val;
    }

    /**
     * Get a global variable or parameter from the global stack frame.
     *
     *
     * @param xctxt The XPath context, which must be passed in order to
     * lazy evaluate variables.
     *
     * @param index Global variable index relative to the global stack
     * frame bottom.
     *
     * @return The value of the variable.
     *
     * @throws TransformerException
     */
    public XObject getGlobalVariable(XPathContext xctxt, final int index) throws TransformerException {

        XObject val = _stackFrames[index];

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return val;
    }

    /**
     * Get a global variable or parameter from the global stack frame.
     *
     *
     * @param xctxt The XPath context, which must be passed in order to
     * lazy evaluate variables.
     *
     * @param index Global variable index relative to the global stack
     * frame bottom.
     *
     * @return The value of the variable.
     *
     * @throws TransformerException
     */
    public XObject getGlobalVariable(XPathContext xctxt, final int index, boolean destructiveOK) throws TransformerException {

        XObject val = _stackFrames[index];

        // Lazy execution of variables.
        if (val.getType() == XObject.CLASS_UNRESOLVEDVARIABLE)
            return (_stackFrames[index] = val.execute(xctxt));

        return destructiveOK ? val : val.getFresh();
    }

    /**
     * Get a variable based on it's qualified name.
     * This is for external use only.
     *
     * @param xctxt The XPath context, which must be passed in order to
     * lazy evaluate variables.
     *
     * @param qname The qualified name of the variable.
     *
     * @return The evaluated value of the variable.
     *
     * @throws javax.xml.transform.TransformerException
     */
    public XObject getVariableOrParam(XPathContext xctxt, com.sun.org.apache.xml.internal.utils.QName qname) throws javax.xml.transform.TransformerException {

        // J2SE does not support Xalan interpretive
        /*
        com.sun.org.apache.xml.internal.utils.PrefixResolver prefixResolver =
        xctxt.getNamespaceContext();
        
        // Get the current ElemTemplateElement, which must be pushed in as the
        // prefix resolver, and then walk backwards in document order, searching
        // for an xsl:param element or xsl:variable element that matches our
        // qname.  If we reach the top level, use the StylesheetRoot's composed
        // list of top level variables and parameters.
        
        if (prefixResolver instanceof com.sun.org.apache.xalan.internal.templates.ElemTemplateElement)
        {
        
        com.sun.org.apache.xalan.internal.templates.ElemVariable vvar;
        
        com.sun.org.apache.xalan.internal.templates.ElemTemplateElement prev =
        (com.sun.org.apache.xalan.internal.templates.ElemTemplateElement) prefixResolver;
        
        if (!(prev instanceof com.sun.org.apache.xalan.internal.templates.Stylesheet))
        {
        while ( !(prev.getParentNode() instanceof com.sun.org.apache.xalan.internal.templates.Stylesheet) )
        {
          com.sun.org.apache.xalan.internal.templates.ElemTemplateElement savedprev = prev;
        
          while (null != (prev = prev.getPreviousSiblingElem()))
          {
            if (prev instanceof com.sun.org.apache.xalan.internal.templates.ElemVariable)
            {
              vvar = (com.sun.org.apache.xalan.internal.templates.ElemVariable) prev;
        
              if (vvar.getName().equals(qname))
                return getLocalVariable(xctxt, vvar.getIndex());
            }
          }
          prev = savedprev.getParentElem();
        }
        }
        
        vvar = prev.getStylesheetRoot().getVariableOrParamComposed(qname);
        if (null != vvar)
        return getGlobalVariable(xctxt, vvar.getIndex());
        }
        */

        throw new javax.xml.transform.TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_VAR_NOT_RESOLVABLE, new Object[] { qname.toString() })); //"Variable not resolvable: " + qname);
    }
} // end VariableStack
